'''
说明：使用snap7读取西门子PLC的DB块数据，提供一个Modbus_Tcp服务端并映射数据，通过MQTT上报
    使用了snap7库，modbus_tk库，paho_mqtt模块
    snap7官网：https://snap7.sourceforge.net/
    Paho-mqtt：https://pypi.org/project/paho-mqtt/#description
作者：工控万金油
首发日期：2024年5月18日
包含文件：Snap7Client3.4.2.py，Mqtt03.py，public_lib.py
声明：我的代码使用snap7、modbus_tk、paho_mqtt、flask等开源模块，感谢他们！
更新：
2025年1月18日：更新版本号为3.3.0，新增web api接口，可通过http请求读写PLC数据块
2025年4月28日：更新版本号为3.4.1，新增IEC104协议，支持通过该协议读写PLC的DB块数据
2025年5月28日：更新版本号为3.4.2，修复上报模板=@@@时，不上报数据的bug
2025年7月02日：更新版本号为3.5.0，弃用modbus-tk库，新增modbus tcp服务端，重写modbus交互方法
2025年11月05日：更新版本号为3.5.1，把点位的数据类型字符统一转成小写
2025年11月08日：更新版本号为3.5.2，增加对M区数据的读写DB编号地址为0时即是读写M区的数据
'''
import snap7,time,json,struct,Mqtt03,copy,ctypes,public_lib
import snap7.util as util
import threading as thd
import web_api
import logging
import iec104_Server
import Modbus_Server
# 禁用日志
logger = logging.getLogger('snap7')
logger.setLevel(logging.CRITICAL) 
VERSION = '3.5.2'
UPDATE = '2025年11月08日'
EXAMPLE = 0             # 实例      
DEVICE_IP = 1           # 设备IP
MODBUS_ID = 2           # modbus的ID
PLC_NAME = 3            # PLC名称
MODBUS_DB_SIZE = 4      # modbus的DB块大小
MODBUS_POINT = 5        # modbus的点位
SNAP7_POINT = 6         # snap7的点位
PLC_STATE = 7           # PLC连接状态
MODBUS_TMP = 21         # modbus临时变量
SNAP7_RW_POINT = 9      # snap7有读写权限的点位
MB_CMP_POINT = 10       # snap7读写的点位，作比较用，判断modbus数据块数据是否有变化，有则写入到plc
DB_MAN_MIN = 11         # 数据块起始结束地址
SNAP7_RD_TMP = 12       # snap7读取DB块数据临时变量
FIRST_RUN = 13          # 第一次运行标志
SNAP7_READ_ERR = 14     # 读取DB块数据失败标志,使程序定时重试时不重复打印错误信息
SNAP7_READ_EN = 15      # snap7读取数据标志,用于modbus_tk线程判断是否写入plc数据
PLC_CONNECT_FAIL = 16   # PLC连接失败标志
SNAP7_READ_SUCCESS = 17 # snap7读取成功标志
PLC_RACK = 18           # PLC机架号
PLC_SLOT = 19           # PLC插槽号
IEC104_POINT = 20       # IEC104协议的点位
IEC104_TMP = 22         # IEC104协议临时变量
LOGGER_FILE = './snap7/snap7_log.txt'
Modbus_Server.LOGGER_FILE = LOGGER_FILE
# 自动检测程序或脚本的运行路径
public_lib.run_path(__file__)    
# snap7读的数据转成合适的modbus点位表
def s7_to_mb(dic):
    def convert_value(data):
        # 转换成无符号数值
        '''
        data[3] = 数据类型
        data[-1] = 数据值  
        VALUE_TYPE = 3
        VALUE = -1
        '''
        # 将32位二进制数据拆分成两个16位二进制数据
        def float_to_int16s(f):
            b = struct.pack('f', f)
            i1,i2 = struct.unpack('HH', b)
            return [i2, i1]
        # 双字拆分为单字
        def DW2W(value):
            temp_H = (value&0xffff0000)>>16
            temp_L = value&0xffff
            return [temp_H,temp_L]
        temp_data=[0,0]
        # 把数据转成字
        if data[3] in [ 'Real' ,'real']:
            temp = float_to_int16s(data[-1])
            temp_data[0] = temp[0]&0xffff  #h和0xffff与是把有符号整型转成无符号整型，因为写入modbus从站的数据块里的值必须无符号
            temp_data[1] = temp[1]&0xffff
        elif data[3] in [ 'DWord' ,'dword' ,'DInt' ,'dint']:    
            temp = DW2W(data[-1])
            temp_data[0] = temp[0]&0xffff
            temp_data[1] = temp[1]&0xffff
        elif data[3] in [ 'Bool' ,'bool' , 'Int' , 'int' , 'Word' , 'word', 'Byte' , 'byte']:
            del temp_data[-1]
            temp_data[0] = data[-1]&0xffff
        else:
            del temp_data[0]
            temp_data[-1] = 404
        return temp_data
    return [value for data in dic.values() for value in convert_value(data)]
# 读取点位文件（json格式）
def RD_file(csv, json_fname, id=0):
    # 计算数据块的最大最小地址
    def max_min_size(dic):
        db_dic = {}
        ADDR =1
        SIZE = 2
        for tag in dic:
            db_num = dic[tag][0]                 
            while True:
                if db_num in db_dic:
                    # db_dic[db_num]=db块字典，[dic[tag][ADDR]]=起始地址键
                    key_addr = dic[tag][ADDR]
                    value_size = dic[tag][SIZE]
                    # 如果字典的键已存在变量地址，则取最大值
                    if key_addr in db_dic[db_num].keys():
                        if db_dic[db_num][key_addr] > value_size:
                            value_size = db_dic[db_num][key_addr]
                    db_dic[db_num][key_addr] = value_size
                    break
                else:
                    db_dic[db_num] = {} # 给DB块添加一个空字典
        for db_num in db_dic:
            max_addr = max(db_dic[db_num])                          #key=点位地址
            minAddr = min(db_dic[db_num])       
            max_addr_size = db_dic[db_num][max_addr]
            maxAddr = int(max_addr) + max_addr_size
            cmp = max(db_dic[db_num].values())
            if maxAddr - minAddr == 1 and cmp != 1:
                maxAddr += cmp-1
            result = [minAddr,maxAddr] # maxAddr 比实际大1
            db_dic[db_num] = result
        return db_dic
    # 写入文件。输入字典，写入json格式文件
    def WD_file(nfile, data):
        with open(nfile,'w',encoding='utf-8') as f:
            temp = json.dumps(data,indent=1,ensure_ascii=False)
            f.write(temp)
    # 生成modbus点位表和上位机数据标识、数据类型
    def WD_modbus_table(nfile, data, tag, id=0):
        global mb_ip,mb_port
        with open(nfile,'w') as f:
            TYPE = 3
            PERMISSION = 4
            MB_ADDR = 6
            MB_LEN = 7
            temp_list = ['名称','地址','长度','数据类型','权限','上位机数据标识','上位机数据类型','\n']
            f.write(f'Modbus服务端地址：{mb_ip}\nModbus服务端端口：{mb_port}\nModbus服务端ID：{id}\n')
            f.write(','.join(['名称','地址','长度','数据类型','权限','上位机数据标识','上位机数据类型','\n']))
            for i in data:
                temp_list[0] = i                        # 变量名
                temp_list[1] = str(data[i][MB_ADDR])    # 地址
                temp_list[2] = str(data[i][MB_LEN])     # 长度
                temp_list[3] = str(data[i][TYPE])       # 数据类型
                temp_list[4] = data[i][PERMISSION]      # 权限
                temp_list[5] = f'{tag}@{i}'             # 上位机数据标识
                if data[i][3] in ['byte','int','word','Byte','Int','Word']:
                    temp_list[6] = 'Int'
                elif data[i][3] in ['Real','real']:
                    temp_list[6] = 'Float'
                elif data[i][3] in ['Dword','dword','Dint','dint']:
                    temp_list[6] = 'Double'
                elif data[i][3] in ['bool','Bool']:
                    temp_list[6] = 'Boolean'
                f.write(','.join(temp_list))    
    # 根据数据类型判断数据长度
    def convert_size(Dtype):
        if Dtype in ['Real','real','DWord','Dword','dword','Dint','DInt','dint']:
            return 4
        elif Dtype in ['word','Word','int','Int']:
            return 2
        elif Dtype in ['Byte','byte','bool','Bool']:
            return 1
    # 读取数据点表转换
    def convert_list(nfile, ecd):
        with open(nfile,encoding=ecd) as f:
            data = f.readlines()
        del data[0]
        dic = {}
        iec104_list = {i:[] for i in [0x0d,0x0b,0x01]} # 50=real,49=int,45=bool
        for i in data:
            if i == '\n':
                continue    # 跳过空行
            row = i[:-1]    #去掉换行符
            row = row.split(',')    
            row[1] = row[1].lower() # 数据类型字符转成小写
            try:
                # row[0]=变量名称，row[3]=数据块地址，row[2]=偏移量，row[1]=数据类型，row[4]=权限,row[5]= iec104i地址
                DataSize = convert_size(row[1])
                try:
                    ioa = int(row[5])
                except:
                    ioa = 0
                dic[row[0]] = [int(row[3]),eval(row[2]),DataSize,row[1],row[4],ioa]
            except  Exception as err:
                # err='检查数据块，其实地址和大小是否出现非数字！'
                public_lib.info(LOGGER_FILE,0,'RD_File>convert_list->',err)
            try:
                addr = int(row[5])
                value = 0
                type_id = 0
                wd_id = 0
                if row[1] in ['real','Real'] :
                    type_id = 0x0d
                    wd_id = 50
                elif row[1] in ['int','Int']:
                    type_id = 0x0b
                    wd_id = 49
                elif row[1] in ['bool','Bool']:
                    type_id = 0x01
                    wd_id = 45
                single_list = [addr,value,type_id,wd_id,row[0]]
                iec104_list[type_id].append(single_list)
            except  Exception as err:
                pass
        return dic,iec104_list
    # 点表增加mb_db块的读取点位
    def add_mb_db(dic):
        start_num = 0
        num = 0
        for_num = len(dic)
        for row in dic:
            if dic[row][3] in ['Real','real','dint','DInt','DWord','dword','Dword','Dint']:
                dic[row] += [start_num,2,0]
                start_num += 2
            else:
                dic[row] += [start_num,1,0]
                start_num += 1
            num += 1
            if num == for_num:
                mb_db_size = dic[row][-3]+dic[row][-2]  #modbus 数据块长度
        return dic,mb_db_size
    njson = f'./snap7/{json_fname}.json'
    try:
        temp_data,iec104_data = convert_list(csv,'gbk')     # 读取点位文件
        WD_file(njson,temp_data)                # 写入json格式文件
        read_dic = copy.deepcopy(temp_data)     # 复制字典
        rd_dic,mbk_size = add_mb_db(read_dic)   # 增加mb_db块的读取点位
        WD_modbus_table(f'./snap7/{json_fname}_modbus点位表.csv',rd_dic,json_fname,id)# 生成modbus点位表
        dbmms = max_min_size(rd_dic)            # 计算数据块的最大最小地址
    except Exception as err:
        # err='检查是否其他程序打开了要读取的文件！'
        public_lib.info(LOGGER_FILE,0,'RD_File->',err)
    return rd_dic,mbk_size,dbmms,iec104_data
def uint_to_int(data):
    # 无符号整型转有符号整型
    temp = ctypes.c_int16(data)
    temp = ctypes.pointer(temp)[0]
    return temp
def int_to_uint(data):
    # 有符号整型转无符号整型
    temp = ctypes.c_uint16(data)
    temp = ctypes.pointer(temp)[0]
    return temp
def uint32_to_int32(data):
    # 无符号32位整型转有符号32位整型
    temp = ctypes.c_int32(data)
    temp = ctypes.pointer(temp)[0]
    return temp
def int32_to_uint32(data):
    # 有符号32位整型转无符号32位整型
    temp = ctypes.c_uint32(data)
    temp = ctypes.pointer(temp)[0]
    return temp
def ubyte_to_byte(data):
    # 无符号字节转有符号字节
    temp = ctypes.c_byte(data)
    temp = ctypes.pointer(temp)[0]
    return temp
def byte_to_ubyte(data):
    # 有符号字节转无符号字节
    temp = ctypes.c_ubyte(data)
    temp = ctypes.pointer(temp)[0]
    return temp
# snap7类
class Snap7Client():
    '''
    支持的数据类型：real，int，dint，word，dword，byte，string（后续支持），char（后续支持），bool
    '''
    def __init__(self, params, max_size=0, rd_freq=1) -> None:
        '''
        params:[[ip,rack,slot,db_num,start,size,type,mb_tag,mb_type,mb_id],...]
        max_size:最大数据块大小
        rd_freq:读取频率,单位秒
        '''
        self.params = copy.deepcopy(params) 
        self.temp_para = []
        self.client = [sgl_param[EXAMPLE] for sgl_param in params]
        self.keepalive = 1
        self.consuccess = 0
        self.max_size = max_size
        self.db_data = {}
        self.freq = rd_freq
        self.exit = 0
    # 字节数组转换-读
    def convert_bytearray(self, data, type, boolnum):
        def ubyte_to_byte(data):
            temp = ctypes.c_byte(data)
            temp = ctypes.pointer(temp)[0]
            return temp
        if type == 'Real' or type == 'real':
            temp_data=util.get_real(data,0)
            # 取小数点后5位
            temp_data = round(temp_data,5)
        elif type == 'Int' or type == 'int':
            temp_data=util.get_int(data,0)
        elif type == 'DInt' or type == 'dint':
            temp_data=util.get_dint(data,0)  
        elif type == 'Word' or type == 'word':
            temp_data=util.get_word(data,0)  
        elif type == 'DWord' or type == 'dword':
            temp_data=util.get_dword(data,0)  
        elif type == 'Byte' or type == 'byte':
            temp_data=util.get_byte(data,0)
            # temp_data=ubyte_to_byte(temp_data)
        elif type == 'String' or type == 'string':
            temp_data=util.get_string(data,0)
        elif type == 'Char' or type == 'char':
            temp_data=util.get_char(data,0)
        elif type == 'Bool' or type == 'bool':
            temp_data=util.get_bool(data,0,boolnum)
            if temp_data:
                temp_data = 1
            else:
                temp_data = 0
        else:
            return data
        return temp_data
    # 字节组转换-写
    def convert_data(self, wdata, dtype, boolnum):
        if dtype in ['Real','real']:
            bry_data = bytearray(4)
            temp_wdata = util.set_real(bry_data,0,wdata)
        elif dtype in ['DWord','dword','DInt','dint','Dword','Dint']:
            bry_data = bytearray(4)
            util.set_dword(bry_data,0,wdata)
            temp_wdata = bry_data
        elif dtype in ['bool','Bool']:
            if wdata >= 1:
                wdata = 1
            else:
                wdata = 0
            bry_data = bytearray(1)
            temp_wdata = util.set_bool(bry_data,0,boolnum,wdata)
            return bry_data
        elif dtype in ['Word','word','Int','int']:
            bry_data = bytearray(2)
            temp_wdata = util.set_word(bry_data,0,wdata)
        elif dtype in ['Byte','byte']:
            if wdata >= 255:
                wdata = 255
            bry_data = bytearray(1)
            temp_wdata = util.set_byte(bry_data,0,wdata)
        return temp_wdata
        pass
    # 新建实例
    def con_plc(self, ip, rack, slot, comm_err):
        try:
            # connect(address(ip地址): str, rack（机架号）: int, slot（卡槽号）: int)
            self.plc = snap7.client.Client()
            self.plc.set_connection_type(3)             # 设置连接类型为S7基本
            self.plc.connect(ip,rack,slot)              # 设置参数连接plc
            self.com_state = self.plc.get_connected()   # 获取连接状态
            if self.com_state:
                public_lib.info(LOGGER_FILE,1,'设备连接',f'成功连接PLC {ip}')
                return self.plc,1,0
            else:
                return self.plc,0,0
        except Exception as err:
            if comm_err == 0:
                public_lib.info(LOGGER_FILE,0,f'Snap7Client>con_plc[{ip}]->',f'{err}')
            return self.plc,0,1
    def db_read(self, plcname, db_num, start,size):
        # 截取字节数组
        for sgl_param in self.params:
            if sgl_param[PLC_NAME] == plcname:
                rd_db_datas = sgl_param[SNAP7_RD_TMP]
                for j in rd_db_datas:
                    if db_num == j:
                        return rd_db_datas[j][start:(start+size)]
    def get(self, plcName, db_num, start_addr, size,type):
        # 字节数组转常规数据
        start_b = 0
        if type in ['bool','Bool']:
            addr = str(start_addr).split('.')
            start_addr = eval(addr[0])
            if len(addr) == 2:
                start_b = eval(addr[1])
            else:
                start_b = 0
        else:
            start_addr=int(start_addr)
        for sgl_param in self.params:
            if  sgl_param[PLC_STATE] ==0:
                pass
                # return 0
            if sgl_param[PLC_NAME] == plcName:
                # {1: [48, 68], 2: [68, 70]}={db块:[最小地址，最大地址]}
                tmp_start = start_addr - int(sgl_param[DB_MAN_MIN][db_num][0]) 
                if db_num == 0:
                    data = self.db_read(plcName,db_num,tmp_start,size)
                else:
                    data = self.db_read(plcName,db_num,tmp_start,size)
                data = self.convert_bytearray(data,type,start_b)
                return data
    def set(self, plcName, db_num, start_addr, data, dtype, device):
        def set_bit(number, n):
            # 置位字节number（十进制数）的第n位,n = 0~7
            mask = 1 << n
            number = number|mask
            return number 
        def rest_bit(number, n):
            # 复位字节number（十进制数）的第n位,n = 0~7
            a = 0xff
            mask = 1 << n
            b = a ^ mask
            c = number & b
            return c
        def type2num(dtype):
            if dtype in ['Real','real','DWord','dword','DInt','dint','Dword','Dint']:
                return 4
            elif dtype in ['int','Int','Word','word']:
                return 2
            elif dtype in ['Byte','byte','bool','Bool']:
                return 1
        start_b = 0
        # 写单个位
        if dtype in ['bool','Bool']:
            temp = str(start_addr).split('.')
            start_addr = eval(temp[0])
            if len(temp) == 2:
                start_b = eval(temp[1])
            else:
                start_b = 0
            # 获取要设置的位所在的字节
            # temp_get=self.get(plcName,db_num,start_addr,1,'byte')
            if db_num == 0:
                temp_get = device.mb_read(start_addr,1)  # 读取字节序列
            else:
                temp_get = device.db_read(db_num,start_addr,1)  # 读取字节序列
            temp_get = util.get_byte(temp_get,0)            # 字节序列转换为字节
            temp_get = byte_to_ubyte(temp_get)              # 转换为无符号字节  
            if data >= 1:
                # 设置所在字节要置位的位
                data = set_bit(temp_get,start_b)
            else:
                # 设置所在字节要复位的位
                data = rest_bit(temp_get,start_b)
            wdata = self.convert_data(data,'byte',start_b)  # 要写入的数据转字节序列
        else:
            start_addr = int(start_addr)
            wdata = self.convert_data(data,dtype,start_b)     # 要写入的数据转字节序列
        for sgl_param in self.params:
            if  sgl_param[PLC_STATE] == 0:
                pass
            else:
                if sgl_param[PLC_NAME] == plcName:
                    if db_num == 0:
                        size = type2num(dtype)
                        sgl_param[EXAMPLE].mb_write(start_addr,size,wdata)
                    else:
                        sgl_param[EXAMPLE].db_write(db_num,start_addr,wdata)
                    time.sleep(0.05)
                    
    # 实时读PLC数据
    def now_read(self, rd_freq=1):
        #0=实例，1=ip，2=ID,3=modbus块名称，4=modbus块长度，5=modbus点位，6=snap7点位 7=plc连接状态  8=  9= 10=  11= 12=snap7读到的数据 13=首次运行，14=
        while True:
            if self.exit == 1:
                break
            for sgl_param in self.params:
                if sgl_param[PLC_STATE] == 1:
                    db_data = {}
                    # sgl_param[11] = DB块最大值，字典{1：12，2：22}
                    for j in sgl_param[DB_MAN_MIN]:
                        try:
                            min_addr = int(sgl_param[DB_MAN_MIN][j][0])
                            max_addr = int(sgl_param[DB_MAN_MIN][j][1])
                            if j == 0:
                                data = sgl_param[EXAMPLE].mb_read(min_addr,max_addr-min_addr)  #sgl_param[11] = 数据块最小和最大地址，[j] = 数据块
                            else:
                                data = sgl_param[EXAMPLE].db_read(j,min_addr,max_addr-min_addr)  #sgl_param[11] = 数据块最小和最大地址，[j] = 数据块
                            db_data[j] = copy.deepcopy(data)
                            sgl_param[FIRST_RUN] = 1
                            sgl_param[MODBUS_TMP][3] = True
                        
                        except Exception as err:
                            sgl_param[SNAP7_READ_SUCCESS] = 0
                            sgl_param[MODBUS_TMP][3] = False
                            alm_text = str(err)
                            try:
                                if 'Item not available' in alm_text and sgl_param[SNAP7_READ_ERR] == 0:
                                    public_lib.info(LOGGER_FILE,0,f'{sgl_param[DEVICE_IP]},{sgl_param[PLC_NAME]}','有些点位的块地址不存在或偏移量超过2043，请检查点位块地址及偏移量是否正确！')
                                    sgl_param[SNAP7_READ_ERR] = 1                                   
                                sgl_param[EXAMPLE].disconnect()
                                sgl_param[PLC_STATE] = 0
                                public_lib.info(LOGGER_FILE,0,f'{sgl_param[DEVICE_IP]}已断开连接',err)
                                break
                            except Exception as err:
                                public_lib.info(LOGGER_FILE,0,f'now_read_err->',err)
                    sgl_param[SNAP7_READ_SUCCESS] = 1
                    sgl_param[SNAP7_RD_TMP] = copy.deepcopy(db_data)
                else:
                    sgl_param[SNAP7_READ_SUCCESS] = 0
            time.sleep(rd_freq)
    def main(self):
        # ['实例1','127.0.0.1',1,'plc1',10,[],{},0,30]
        t0 = 0
        t1 = 0
        # 线程的args参数，需要使用元组
        thd1=thd.Thread(target=self.now_read,args=(self.freq,))
        thd1.daemon = 1
        thd1.start()
        first_scan = 0
        t_cf = 5
        item_err = 0
        while True:
            if self.exit == 1:
                break
            num = 0
            if t_cf >= t1:
                for sgl_param in self.params:
                    # sgl_param[PLC_STATE] 客户端连接状态
                    if sgl_param[PLC_STATE] == 0:
                        # sgl_param[FIRST_RUN]首次连接成功
                        sgl_param[FIRST_RUN] = 0
                        sgl_param[EXAMPLE],sgl_param[PLC_STATE],sgl_param[PLC_CONNECT_FAIL] = self.con_plc(sgl_param[DEVICE_IP],
                                                                                             sgl_param[PLC_RACK],
                                                                                             sgl_param[PLC_SLOT],
                                                                                             sgl_param[PLC_CONNECT_FAIL]
                                                                                             )
                    num += sgl_param[PLC_STATE]
                    if sgl_param[SNAP7_READ_ERR] == 1:
                        item_err=1
                        break
            if num == len(self.params):
                t_cf = 5
            elif first_scan:
                t_cf = 5
            t0 += 1
            t1 += 1
            time.sleep(1)
            if t1 > t_cf:
                t1 = 0
            first_scan = 1
            if item_err ==1:
                break  
# ----上报的数据处理----
# webapi和mqtt共用
def pub_data(plcName, dic):
    temp_dic = {}
    for tag in dic:
        temp_dic[f'{plcName}@{tag}'] = dic[tag][-1]
    return temp_dic
def Pub_code_data(code_pub, data):
    new_data = code_pub.replace('@@@',str(data))
    new_data = new_data.replace('nan','0')
    return eval(new_data)
# ----上报的数据处理----
# mqtt处理数据线程
def main_mqtt(topic, mqtt_en, Ton_Pub, code_pub):
    global data_all,client_plc,program_exit_en,program_restart
    mqtt_t0 = 0
    Ton_Pub = int(Ton_Pub)
    while True:
        try:
            if program_exit_en == 1:
                break   # 退出线程
            if mqtt_en == '1' and Mqtt03.connect_en:
                # 上报
                if mqtt_t0 >= Ton_Pub:
                    for sgl_data in data_all:
                        if sgl_data[PLC_STATE]:
                            dic0 = copy.deepcopy(sgl_data[SNAP7_POINT])
                            dic0['状态'] = [1]
                            try:
                                #sgl_data[3] plc名称 sgl_data[6] plc点位数据
                                convert_data = Pub_code_data(code_pub,pub_data(sgl_data[PLC_NAME],dic0))                            
                                temp_data = json.dumps(convert_data,indent=1,ensure_ascii=False)                              
                                Mqtt03.publish(topic,temp_data)  
                            except Exception as err:
                                temp_data = json.dumps({'Error':'格式转换出错！'},indent=1,ensure_ascii=False)
                                Mqtt03.publish(topic,temp_data)
                                public_lib.info(LOGGER_FILE,0,'上报数据转换出错->',f'{sgl_data[PLC_NAME]}-{err}')
                        else:
                            convert_data=Pub_code_data(code_pub,pub_data(sgl_data[PLC_NAME],{'状态':[0]}))                            
                            temp_data=json.dumps(convert_data,indent=1,ensure_ascii=False)                              
                            Mqtt03.publish(topic,temp_data)  
                        mqtt_t0 = 0
                # 订阅，判断下发到plc
                if Mqtt03.global_message:
                    try:
                        try:
                            rec_data = json.loads(Mqtt03.global_message)
                            # 收到上位机下发的数据
                            set_data = rec_data['SetProperty']
                            for k,sgl_data in enumerate(data_all):
                                comp_dic = sgl_data[SNAP7_RW_POINT]
                                for i in set_data:
                                    plcname = i.split('@')[0]
                                    tag = i.split('@')[1]
                                    try:
                                        if tag in comp_dic and plcname == sgl_data[3]:
                                            data_type = comp_dic[tag][3]
                                            if data_type in ['int','Int']:
                                                temp = int_to_uint(set_data[i])
                                            elif data_type in ['dint','DInt']:
                                                temp = int32_to_uint32(set_data[i])
                                            elif data_type in  ['byte','Byte']:
                                                temp = byte_to_ubyte(set_data[i])
                                            else:
                                                temp = set_data[i]
                                            # set(plc名称，db块，起始地址，写入数据，数据类型,实例)
                                            client_plc.set(plcName = plcname,
                                                            db_num = comp_dic[tag][0],
                                                            start_addr = comp_dic[tag][1],
                                                            data = temp,
                                                            dtype = comp_dic[tag][3],
                                                            device = client_plc.params[k][0])
                                            if comp_dic[tag][0] != 0:
                                                dtypePrint = f'DB块：{comp_dic[tag][0]}'
                                            else:
                                                dtypePrint = 'MB块'
                                            wd = f'{dtypePrint}，点位名称：{tag}，起始地址：{comp_dic[tag][1]}，写入数据：{set_data[i]}，数据类型：{comp_dic[tag][3]}'
                                            public_lib.info('./snap7/log_snap7_Write.txt',1,f'通过MQTT写入{plcname}',wd)
                                    except Exception as err :
                                        public_lib.info(LOGGER_FILE,0,'MQTT写数据到设备出错->',err)
                        except:
                            pass
                    except Exception as err:
                        public_lib.info(LOGGER_FILE,0,'MQTT接收错误->',err)
                    Mqtt03.global_message=''
        except Exception as err:
           public_lib.info(LOGGER_FILE,0,'MQTT接收或发送出错->',err)
        mqtt_t0 += 1
        time.sleep(1)
# webapi处理数据线程
def main_webapi(freq=1):
    global web_api_exit_en,web_api_en,data_all
    mode = 0
    try:
        key_pub = [key for key in Pub_code_data(code_pub,'1').keys()][0]
        web_api.webapi_send[key_pub] = {}
        mode = 0
    except:
        web_api.webapi_send = {}
        mode = 1
    while True:
        if web_api_exit_en == 1 :
            web_api.app_close() # 关闭webapi
            break
        for sgl_data in data_all:
            if sgl_data[PLC_STATE]:
                dic0 = copy.deepcopy(sgl_data[SNAP7_POINT])
                dic0['状态'] = [1]
                try:
                    #sgl_data[3] plc名称 sgl_data[6] plc点位数据
                    convert_data = Pub_code_data(code_pub,pub_data(sgl_data[PLC_NAME],dic0)) 
                    if mode == 0:                           
                        web_api.webapi_send[key_pub].update(convert_data[key_pub])  
                    else:
                        web_api.webapi_send.update(convert_data)  
                except Exception as err:
                    public_lib.info(LOGGER_FILE,0,'webapi数据转换出错->',f'{sgl_data[PLC_NAME]}-{err}')
            else:
                convert_data=Pub_code_data(code_pub,pub_data(sgl_data[PLC_NAME],{'状态':[0]}))      
                if mode == 0 :                                             
                    web_api.webapi_send[key_pub].update(convert_data[key_pub])
                else:
                    web_api.webapi_send.update(convert_data)  
        # 下发
        if web_api.webapi_received_en :
            web_api.webapi_received_en = 0
            # 收到webapi下发的数据
            try:
                rec_data = web_api.webapi_received
                # 收到上位机下发的数据
                set_data = rec_data['SetProperty']
                for k,sgl_data in enumerate(data_all):
                    comp_dic = sgl_data[SNAP7_RW_POINT]
                    for i in set_data:
                        plcname = i.split('@')[0]
                        tag = i.split('@')[1]
                        try:
                            if tag in comp_dic and plcname == sgl_data[3]:
                                data_type = comp_dic[tag][3]
                                
                                if data_type in ['int','Int']:
                                    temp = int_to_uint(set_data[i])
                                elif data_type in ['dint','DInt']:
                                    temp = int32_to_uint32(set_data[i])
                                elif data_type in  ['byte','Byte']:
                                    temp = byte_to_ubyte(set_data[i])
                                else:
                                    temp = set_data[i]
                                # set(plc名称，db块，起始地址，写入数据，数据类型,实例)
                                client_plc.set(plcName = plcname,
                                                db_num = comp_dic[tag][0],
                                                start_addr = comp_dic[tag][1],
                                                data = temp,
                                                dtype = comp_dic[tag][3],
                                                device = client_plc.params[k][0])
                                if comp_dic[tag][0] != 0:
                                    dtypePrint = f'DB块：{comp_dic[tag][0]}'
                                else:
                                    dtypePrint = 'MB块'
                                wd = f'{dtypePrint}，点位名称：{tag}，起始地址：{comp_dic[tag][1]}，写入数据：{set_data[i]}，数据类型：{comp_dic[tag][3]}'
                                public_lib.info('./snap7/log_snap7_Write.txt',1,f'通过webApi写入{plcname}',wd)
                        except Exception as err :
                            public_lib.info(LOGGER_FILE,0,'webApi写数据到设备出错->',err)
            except:
                # 接收的数据格式错误
                pass
            web_api.webapi_received.clear()
        time.sleep(freq)
# modbus_tcp线程
def main_modbus(ip,port,params,rd_freq=1):
    global program_exit_en,client_plc
    global data_all,mbtcp_en,param_freq
    #合成32位数；模式1：输出浮点；模式2：输出双整型
    def int2_to_double(mode, h, l):
        z0 = hex(h)[2:].zfill(4)
        z1 = hex(l)[2:].zfill(4)
        z = z0+z1
        if mode == 1:
            result = struct.unpack('!f',bytes.fromhex(z))[0]
            return round(result,5)  # 取5位小数
        elif mode == 2:
            result = int(z,16)
            return result
    temp_para = copy.deepcopy(params)
    tmp_dict = {}
    for sgl_param in temp_para:
        tmp_dict[sgl_param[MODBUS_ID]] = [0 for _ in range(sgl_param[MODBUS_DB_SIZE])]
    MBSlave = Modbus_Server.ModbusServer(
        host = ip,
        port = port,
        blocks= tmp_dict
        )
    TON_MB = 0
    WD_EN = 1
    DELAY_RD_EN = 2
    num = 0
    while True:
        if program_exit_en == 1:
            MBSlave.stop_flag = 1
            break
        try:
            if mbtcp_en == '1':
                for k,sgl_data in enumerate(data_all):
                    for tag in sgl_data[SNAP7_RW_POINT]:
                        tag_list = sgl_data[SNAP7_RW_POINT][tag] # 读写点位测点键的值
                        for i in MBSlave.wdList:
                            if i[0] == sgl_data[MODBUS_ID] and tag_list[6] == i[1]: # tag_list[6] 写入的起始地址
                                if tag_list[7] == 1 and type(i[2]) == int:
                                    tmp_data = i[2]
                                    client_plc.set(
                                        plcName = sgl_data[PLC_NAME],
                                        db_num = tag_list[0],
                                        start_addr = tag_list[1],
                                        data = tmp_data,
                                        dtype = tag_list[3],
                                        device = client_plc.params[k][0])
                                    if tag_list[3] in ['bool','Bool','int','Int','byte','Byte'] :
                                        data_display = uint_to_int(tmp_data)
                                    else:
                                        data_display = tmp_data
                                    sgl_data[MODBUS_TMP][WD_EN] = True
                                if tag_list[7] == 2 and type(i[2]) == list :
                                    if tag_list[3] in ['real','Real'] :
                                        tmp_data = int2_to_double(1,i[2][0],i[2][1])
                                    if tag_list[3] in ['dword','DWord','dint','DInt'] :
                                        tmp_data = int2_to_double(2,i[2][0],i[2][1])
                                    client_plc.set(
                                        plcName = sgl_data[PLC_NAME],
                                        db_num = tag_list[0],
                                        start_addr = tag_list[1],
                                        data = tmp_data,
                                        dtype = tag_list[3],
                                        device = client_plc.params[k][0])
                                    sgl_data[MODBUS_TMP][WD_EN] = True
                                    if tag_list[3] in ['dint','DInt']:
                                        data_display = uint32_to_int32(tmp_data)
                                    else:
                                        data_display = tmp_data
                                if sgl_data[MODBUS_TMP][WD_EN] == True:
                                    sgl_data[MODBUS_TMP][WD_EN] = False
                                    # sgl_data[SNAP7_READ_EN] = 0
                                    sgl_data[MODBUS_TMP][DELAY_RD_EN] = True
                                    if tag_list[0] != 0:
                                        dtypePrint = f'DB块：{tag_list[0]}'
                                    else:
                                        dtypePrint = 'MB块'
                                    wd = f'{dtypePrint}，点位名称：{tag}，起始地址：{tag_list[1]}，写入数据：{data_display}，数据类型：{tag_list[3]}'
                                    public_lib.info('./snap7/log_snap7_Write.txt',1,f'通过Modbus写入{sgl_data[PLC_NAME]}',wd)   
                    if sgl_data[SNAP7_READ_EN] == 1 :
                        if sgl_data[MODBUS_TMP][DELAY_RD_EN] == True:
                            sgl_data[MODBUS_TMP][TON_MB] += rd_freq         # 延迟读定时器累加
                            if sgl_data[MODBUS_TMP][TON_MB] > rd_freq*5 :
                                MBSlave.blocks[sgl_data[MODBUS_ID]] = sgl_data[MODBUS_POINT] # 刷新modbus数据块数据
                                sgl_data[MODBUS_TMP][DELAY_RD_EN] = False   # 复位延迟读使能
                                sgl_data[MODBUS_TMP][TON_MB] = 0            # 复位延迟读定时器
                                sgl_data[SNAP7_READ_EN] = 0                 # 复位snap7读使能
                        else:
                            MBSlave.blocks[sgl_data[MODBUS_ID]] = sgl_data[MODBUS_POINT] # 刷新modbus数据块数据
                            sgl_data[SNAP7_READ_EN] = 0                                  # 复位snap7读使能  
                            sgl_data[MODBUS_TMP][TON_MB] = 0
                MBSlave.wdList = []  # 清空modbus弹夹  
                        
            else:
                MBSlave.wdList = []
        #0=实例，1=ip，2=ID,3=modbus块名称，4=modbus块长度，5=modbus点位，6=snap7点位 7=plc连接状态  8=  9= 10=  11= 12= 13=首次运行，14=
        # [实例1，'127.0.0.1',1,'plc1',10,[],{},0,30,{},{},{},{}]
        except Exception as err:
            public_lib.info(LOGGER_FILE,0,'main_modbus出错->',err)
            # time.sleep(1)
        time.sleep(rd_freq*0.5)
# iec104线程
def main_iec104(freq=0.5):
    global iec104_server,data_all
    IEC104_IOA = 5
    type_value = {50: 'real',49: 'int',45: 'bool'}
    while True:
        if iec104_server.stop_flag :
            break
        iec104_data_dict = {i:[] for i in [0x0d,0x0b,0x01]}
        for sgl_data in data_all:
            for key in sgl_data[SNAP7_POINT]:
                try:
                    row = sgl_data[SNAP7_POINT][key]
                    ioa = row[IEC104_IOA]
                    value = row[-1]
                    type_id = 0
                    wd_id = 0
                    tag = key
                    if row[3] in ['real','Real'] :
                        type_id = 0x0d
                        wd_id = 50
                    elif row[3] in ['int','Int']:
                        type_id = 0x0b
                        wd_id = 49
                    elif row[3] in ['bool','Bool']:
                        type_id = 0x01
                        wd_id = 45
                    if ioa!= 0:
                        iec104_data_dict[type_id].append([ioa,value,type_id,wd_id,tag])
                except:
                    pass
        iec104_server.data_real = iec104_data_dict[0x0d]
        iec104_server.data_int = iec104_data_dict[0x0b]
        iec104_server.data_bool = iec104_data_dict[0x01]
        # 写操作
        for iec104_wd in iec104_server.download_list:
            if type(iec104_wd[0]) == int:   
                iec104_wd[0] = type_value[iec104_wd[0]]
            for k,sgl_data in enumerate(data_all):
                sgl_data[IEC104_TMP][0] = True
                comp_dic = sgl_data[SNAP7_RW_POINT]
                plcname = sgl_data[PLC_NAME]
                try:
                    for key in comp_dic:
                        if comp_dic[key][IEC104_IOA] == iec104_wd[1]:
                            data_type = comp_dic[key][3]
                            if data_type in ['int','Int']:
                                temp_wd = int_to_uint(iec104_wd[2])
                            elif data_type in ['dint','DInt']:
                                temp_wd = int32_to_uint32(iec104_wd[2])
                            elif data_type in  ['byte','Byte']:
                                temp_wd = byte_to_ubyte(iec104_wd[2])
                            else:
                                temp_wd = iec104_wd[2]
                            client_plc.set(plcName = plcname,                # plc名称
                                           db_num = comp_dic[key][0],        # db块号
                                           start_addr = comp_dic[key][1],    # 起始地址
                                           data = temp_wd,                   # 写入数据
                                           dtype = comp_dic[key][3],         # 数据类型
                                           device = client_plc.params[k][0]) # 实例
                            sgl_data[IEC104_TMP][0] = False
                            if comp_dic[key][0] != 0:
                                dtypePrint = f'DB块：{comp_dic[tag][0]}'
                            else:
                                dtypePrint = 'MB块'
                            wd = f'{dtypePrint}，点位名称：{key}，起始地址：{comp_dic[key][1]}，写入数据：{temp_wd}，数据类型：{comp_dic[key][3]}'
                            public_lib.info('./snap7/log_snap7_Write.txt',1,f'通过IEC104写入{plcname}',wd)
                except Exception as err:
                    public_lib.info(LOGGER_FILE,0,'IEC104写数据到设备出错->',err)
                if sgl_data[IEC104_TMP][0] == False :
                    iec104_server.download_list = []
        time.sleep(freq*0.5)
# 主线程
def main(params, rd_freq=1):
    # 全局变量data_all用于与modbus，mqtt的数据交互用
    global data_all,client_plc,param_freq,Ton_Pub,code_pub,program_exit_en,program_state_exit
    global topic,mqtt_en
    global mb_ip,mb_port,mbtcp_en
    global web_api_en
    global iec104_en
    data_all = copy.deepcopy(params)
    client_plc = Snap7Client(params,rd_freq=rd_freq)
    thd_main = thd.Thread(target=client_plc.main)
    thd_main.daemon = True
    thd_main.start()
    tmp_en = 0
    # modbus_tcp服务端线程
    if mbtcp_en == '1':
        thd0 = thd.Thread(target=main_modbus,args=(mb_ip,int(mb_port),params,rd_freq))
        thd0.daemon = True
        thd0.start()
    # mqtt服务端线程
    if mqtt_en == '1':
        thd1 = thd.Thread(target=main_mqtt,args=(topic,mqtt_en,Ton_Pub,code_pub))
        thd1.daemon = True
        thd1.start()
    # webapi服务端线程
    if web_api_en == '1':
        thd2 = thd.Thread(target=main_webapi)
        thd2.daemon = True
        thd2.start()
    # iec104服务端线程
    if iec104_en == '1':
        thd3 = thd.Thread(target=main_iec104)
        thd3.daemon = True
        thd3.start()
    while True:
        try:
            num = 0
            # 默认参数模板
            #0=实例，1=ip，2=ID,3=modbus块名称，4=modbus块长度，5=modbus点位，6=snap7点位 7=plc连接状态  
            #8=  9=snap7读写的点位 10=  11=db块最大地址 12= 13=首次运行，14=,15=snap7次读成功标识
            #16=plc连接失败标识,17=snap7主读成功标识
            for sgl_param in client_plc.params:
                # ['实例1','127.0.0.1',1,'plc1',10,[],{},0,30,{},{}]
                # sgl_param[PLC_NAME]plc名称 sgl_param[PLC_STATE]客户端成功链接
                if  sgl_param[PLC_STATE] and len(sgl_param[SNAP7_POINT])!=0 and len(sgl_param[SNAP7_RD_TMP])!=0 and sgl_param[SNAP7_READ_SUCCESS] ==1:
                    for j in sgl_param[SNAP7_POINT]:
                        dic_value = sgl_param[SNAP7_POINT][j]
                        try:
                            plc_name = sgl_param[PLC_NAME]
                            db_num = dic_value[0]       # db块号
                            start_addr = dic_value[1]   # 起始地址
                            size = dic_value[2]         # 读取长度
                            dataType = dic_value[3]     # 数据类型
                            dic_value[8] = client_plc.get(plc_name,db_num,start_addr,size,dataType) # 读取值
                            tmp_en = 1
                        except Exception as err:
                            if sgl_param[SNAP7_READ_ERR] != 1:
                                public_lib.info(LOGGER_FILE,0,f'main_snap7[{sgl_param[PLC_NAME]}]->',err)
                                tmp_en = 0
                            time.sleep(1)
                            try:
                                if sgl_param[EXAMPLE].get_connected():
                                    pass
                                else:
                                    sgl_param[PLC_STATE] = 0
                                    break
                            except:
                                sgl_param[PLC_STATE] = 0
                    sgl_param[MODBUS_POINT] = s7_to_mb(sgl_param[SNAP7_POINT])
                    sgl_param[SNAP7_READ_SUCCESS] = 0
                else:
                    tmp_en = 0
                data_all[num][1:10] = copy.deepcopy(sgl_param[1:10])
                if tmp_en == 1:
                    data_all[num][SNAP7_READ_EN] = 1
                num += 1
            if program_exit_en == 1:
                client_plc.exit = 1
                program_state_exit = 1
                break
        except Exception as err:
            public_lib.info(LOGGER_FILE,0,'Main_Sanp7_0->',err)
            data_all[num][SNAP7_READ_EN] = 0
            time.sleep(1)
        time.sleep(rd_freq)
def fc_parameter():
    global mb_ip,mb_port,param_freq,topic,mbtcp_en,params,temp_para,code_pub,autorun_en,delay_arun
    global topic_ft,topic_fb,Ton_Pub,mss_topic,mqtt_en,plc_para
    global web_api_en
    global iec104_server,iec104_en
    mb_ip = '127.0.0.1'   # iP
    mb_port = 502         # 端口
    param_freq = 1        # 刷新频率1s
    topic = ''            # 主题空
    mbtcp_en = ''         # modbusTcp使能
    params = []           # 参数列表
    temp_para = []        # 参数列表缓存
    code_pub = ''
    autorun_en = 0
    delay_arun = 15
    topic_ft = "file_transfer"
    topic_fb ='faceback'
    webApi_ip = '127.0.0.1'
    webApi_port = 5000
    web_api_en = 0
    iec104_en = 0
    iec104_addr = '127.0.0.1'
    iec104_port = 2404

    # 读取配置文件config.ini
    try:
        read_config = public_lib.cfg_read('config.ini','UTF-8')
        mb_ip       = read_config['mbtcp地址']
        mb_port     = read_config['mbtcp端口']
        param_freq  = float(read_config['读取频率'])
        Ton_Pub     = read_config['上报间隔']
        topic       = str(read_config['发布主题'])
        mss_topic   = str(read_config['订阅主题'])
        mqtt_en     = read_config['MQTT使能']
        mbtcp_en    = read_config['mbtcp使能']
        plc_para    = eval(read_config['控制器参数'])
        code_pub    = read_config['上报格式模板']
        autorun_en  = read_config['开机启动']
        delay_arun  = read_config['自启延时']
        dir_hide    = read_config['隐藏文件夹']
        topic_ft    = f"{topic}/file_transfer"
        topic_fb    = f'{topic}/faceback'
        webApi_ip   = read_config['webApi地址']
        webApi_port = read_config['webApi端口']
        web_api_en  = read_config['webApi使能']
        iec104_en   = read_config['iec104使能']
        iec104_addr = read_config['iec104地址']
        iec104_port = read_config['iec104端口']
        iec104_coa  = read_config['iec104站地址']
    except Exception as err:
        public_lib.info(LOGGER_FILE,0,'读取配置文件出错->',err)
    try:
        '''
        默认参数模板
        0=实例，1=ip，2=modbus ID,3=modbus块名称，4=modbus块长度，5=modbus点位，6=snap7点位 7=plc连接状态  
        8=NAN  9=snap7读写权限的点位 10=modbus读写比较点位  
        11=db块起始和结束地址 12=snap7读取db块数据临时变量 
        13=首次运行，14=db块读取失败标志,15=snap7次读成功标识
        16=plc连接失败标识,17=snap7主读成功标识，18=plc机架号，19=plc槽位,20=iec104点位
        21= MODBUS TMP(['Ton','WD_EN','DELAY_RD_EN','snap7_now_read_en']),22= iec104 临时点位
        '''
        para_defaul=['实例名称','127.0.0.1',1,'plc1',10,[],{},0,0,{},{},{},{},0,0,0,0,0,'机架号','槽号','iec104点位',[0.0,False,False,False],[False]]
        temp_para = [para_defaul for _ in range(len(plc_para))]
        for num in range(len(temp_para)):
            temp_para[num][DEVICE_IP] = plc_para[num][0]      #IP
            temp_para[num][MODBUS_ID] = plc_para[num][1]      #ID
            temp_para[num][PLC_NAME] = plc_para[num][2]      #名称
            temp_para[num][PLC_RACK] = plc_para[num][4]      #机架号
            temp_para[num][PLC_SLOT] = plc_para[num][5]      #槽号
            plc_id = plc_para[num][1]               #ID
            # PLC点位源csv文件
            sf = plc_para[num][3]                   
            # 要生成的json点位文件
            # rf=生成点位表json文件的名称
            rf = temp_para[num][3]
            # 需要读取控制器的变量,块大小长度
            # db_max_size：DB块地址和最大长度（sf=点位csv文件，rf=名称）
            dic,mb_db_size,db_max_min_size,iec104_data = RD_file(sf,rf,plc_id)
            temp_para[num][MODBUS_DB_SIZE] = mb_db_size          #modbus块长度
            temp_para[num][SNAP7_POINT] = dic                    #snap7点位
            temp_comp_dic=copy.deepcopy(dic)
            # comp_dic=读写的点位统计,作比较用
            comp_dic={num:temp_comp_dic[num] for num in temp_comp_dic if temp_comp_dic[num][4] == '读写'}
            temp_para[num][SNAP7_RW_POINT] = comp_dic
            temp_para[num][DB_MAN_MIN] = db_max_min_size
            temp_para[num][IEC104_POINT] = iec104_data
            params.append(copy.deepcopy(temp_para[num]))    #汇总参数列表       
    except Exception as err:
        public_lib.info(LOGGER_FILE,0,'启动时请勿使用其他程序打开生成的文件->',err)
    try:
        dir_hide = int(dir_hide)
        if dir_hide <= 0:
            dir_hide = 0
        if dir_hide > 1:
            dir_hide = 1
    except:
        dir_hide = 0
    # 文件夹不存在时自动新建
    public_lib.mk_folder('snap7',hide=dir_hide)
    public_lib.mk_folder('TMP',hide=dir_hide)
    # 日志文件不存在时运行程序自动创建
    public_lib.exists_file(LOGGER_FILE,f'{title_text2}\n{myinfo}\n')
    # 程序自启动判断
    try:
        public_lib.autorun_snap7(int(autorun_en),LOGGER_FILE)
    except Exception as err:
        public_lib.info(LOGGER_FILE,0,'autorun_snap7->',err)
    #MQTT线程
    if mqtt_en == '1':
        thd10 = thd.Thread(target=Mqtt03.main)
        thd10.daemon = True
        thd10.start()
    # webapi线程
    if web_api_en == '1':
        web_api.app_run(ip=webApi_ip,port=webApi_port)
        public_lib.info(LOGGER_FILE,1,'webApi服务运行',f'访问连接http://{webApi_ip}:{webApi_port}/get')
    # iec104线程
    if iec104_en == '1':
        iec104_data_dict = {i:[] for i in [0x0d,0x0b,0x01]}
        for i in params:
            tmp_data = i[IEC104_POINT]              # iec104点位
            for code in  [0x0d,0x0b,0x01] :
                try:
                    iec104_data_dict[code].extend(tmp_data[code])
                except:
                    pass
        iec104_Server.LOGGER_FILE = LOGGER_FILE     # 日志文件名
        try:
            iec104_Server.ADDR = int(iec104_coa)    # 站地址
        except:
            iec104_Server.ADDR = 1
        try:
            iec104_port = int(iec104_port)
        except:
            iec104_port = 2404
        iec104_server = iec104_Server.iec104Server(iec104_addr,
                                                    iec104_port,
                                                    data_real=iec104_data_dict[0x0d],
                                                    data_int=iec104_data_dict[0x0b],
                                                    data_bool=iec104_data_dict[0x01]
                                                    )
        thd11 = thd.Thread(target=iec104_server.run)
        thd11.daemon = True
        thd11.start()
    # snap7主线程
    main(params,param_freq/1)
# 启动程序
def start_up():
    thd20 = thd.Thread(target=fc_parameter)
    thd20.daemon = 1
    thd20.start()
if __name__ == '__main__':   
    program_exit_en = 0     # 程序退出使能
    web_api_exit_en = 0     # webapi退出使能
    program_state_exit = 0  # 程序退出状态
    program_restart = 0     # 程序重启标志
    title_text = '''
            '####::'######:::'######::'########:'########::::'###::::'##::::'##:::::::'######::'##::: ##:
            . ##::'##... ##:'##... ##:... ##..:: ##.....::::'## ##::: ###::'###::::::'##... ##: ###:: ##:
            : ##:: ##:::..:: ##:::..::::: ##:::: ##::::::::'##:. ##:: ####'####:::::: ##:::..:: ####: ##:
            : ##:: ##:::::::. ######::::: ##:::: ######:::'##:::. ##: ## ### ##:::::: ##::::::: ## ## ##:
            : ##:: ##::::::::..... ##:::: ##:::: ##...:::: #########: ##. #: ##:::::: ##::::::: ##. ####:
            : ##:: ##::: ##:'##::: ##:::: ##:::: ##::::::: ##.... ##: ##:.:: ##:'###: ##::: ##: ##:. ###: 
            '####:. ######::. ######::::: ##:::: ########: ##:::: ##: ##:::: ##: ###:. ######:: ##::. ##:
            ....:::......::::......::::::..:::::........::..:::::..::..:::::..::...:::......:::..::::..::
            '''    
    a = '''
        
    '''
    title_text2='''        
                icsteam.cn                                   
        '''
    print(title_text)   #打印标题
    myinfo = f'作者：工控万金油\n版本：{VERSION}\n首发日期：2024年05月18日\n更新日期：{UPDATE}\n邮箱：i@icssteam.cn\n声明：我的代码使用了snap7、paho_mqtt、flask等开源库，感谢他们！'
    print(myinfo)       #打印作者信息
    public_lib.mk_folder('snap7')
    # 启动程序
    start_up()
    ac0 = 0         # 计数器
    restart_en = 0  # 重启标志
    while True:
        try:
            if Mqtt03.program_restart == 1:
                program_exit_en = 1
                iec104_server.stop_flag = True
                web_api_exit_en = 1
                Mqtt03.mqtt_exit = 1
                public_lib.info(LOGGER_FILE,1,'记录','程序重启中！')
                time.sleep(5)
                if program_state_exit == 1:
                    program_state_exit = 0
                    program_exit_en = 0
                    web_api_exit_en = 0
                    Mqtt03.program_restart = 0
                    Mqtt03.mqtt_exit = 0
                    time.sleep(5)
                    start_up()  # 启动程序
                    restart_en=1
            if restart_en == 1 and Mqtt03.connect_en:
                restart_en = 0
                Mqtt03.publish(Mqtt03.topic_fb,Mqtt03.json.dumps({"restart":1}))    # 发布重启成功消息
            time.sleep(2)
        except Exception as err:
            if Mqtt03.program_restart == 1:
                Mqtt03.publish(Mqtt03.topic_fb,json.dumps({"main_err":err}))        # 发布重启错误信息
            time.sleep(5)
    


     
